// Copyright 2022-2023 Tigris Data, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package search

import (
	"testing"

	"github.com/stretchr/testify/require"
	"github.com/tigrisdata/tigris/internal"
	"github.com/tigrisdata/tigris/schema"
	"github.com/tigrisdata/tigris/util"
)

func TestMutateSearchDocument(t *testing.T) {
	cases := []struct {
		index          []byte
		doc            []byte
		stripId        bool
		expTransformed []byte
		expOriginal    []byte
		expError       string
	}{
		{
			// id is autogenerated, not present in schema
			[]byte(`{"title":"t1","properties":{"a":{"type":"integer"},"b":{"type":"string"},"c":{"type":"object","properties":{"e":{"type":"integer"},"f":{"type":"string"},"g":{"type":"object","properties":{"h":{"type":"string"},"i":{"searchIndex":false,"type":"object","properties":{}}}}}},"d":{"type":"object","properties":{}}}}`),
			[]byte(`{"a":1,"b":2,"c":{"e":1,"f":2,"g":{"h":"FOO","i":{"i_nest":"index i nested"}}},"d":{"agent":"java client","message":"alright"}}`),
			true,
			[]byte(`{"a":1,"b":2.0,"c.e":1,"c.f":2.0,"c.g.h":"FOO","c.g.i.i_nest":"index i nested","d":{"agent":"java client","message":"alright"}}`),
			[]byte(`{"a":1,"b":2,"c":{"e":1,"f":2,"g":{"h":"FOO","i":{"i_nest":"index i nested"}}},"d":{"agent":"java client","message":"alright"}}`),
			"",
		}, {
			// id is added top level on transformed payload, same as field value "c"
			[]byte(`{"title":"t1","properties":{"a":{"type":"integer"},"b":{"type":"string"},"c":{"type":"string","id":true}}}`),
			[]byte(`{"a":1,"b":"foo","c":"1"}`),
			false,
			[]byte(`{"a":1,"b":"foo","c":"1", "id":"1"}`),
			[]byte(`{"a":1,"b":"foo","c":"1"}`),
			"",
		}, {
			// top level "id" is set as "id"
			[]byte(`{"title":"t1","properties":{"a":{"type":"integer"},"b":{"type":"string"},"id":{"type":"string","id":true}}}`),
			[]byte(`{"a":1,"b":"foo","id":"1"}`),
			false,
			[]byte(`{"a":1,"b":"foo","id":"1"}`),
			[]byte(`{"a":1,"b":"foo","id":"1"}`),
			"",
		}, {
			// top level "id" is set as "id" but missing in payload
			[]byte(`{"title":"t1","properties":{"a":{"type":"integer"},"b":{"type":"string"},"id":{"type":"string","id":true}}}`),
			[]byte(`{"a":1,"b":"foo"}`),
			false,
			nil,
			nil,
			"index has explicitly marked 'id' field as 'id' but document is missing that field",
		}, {
			// id is added top level on transformed payload, same as field value "c"
			[]byte(`{"title":"t1","properties":{"a":{"type":"integer"},"b":{"type":"string"},"c":{"type":"string","id":true}}}`),
			[]byte(`{"a":1,"b":"foo"}`),
			false,
			nil,
			nil,
			"index has explicitly marked 'c' field as 'id' but document is missing that field",
		}, {
			// nested id in the schema
			[]byte(`{"title":"t1","properties":{"a":{"type":"string"},"id":{"type":"string"},"c":{"type":"object","properties":{"id":{"type":"string", "id": true}}}}}`),
			[]byte(`{"a":"foo","id":"top-level-id","c":{"id":"nested_id"}}`),
			false,
			[]byte(`{"a":"foo","_tigris_id":"top-level-id","c.id":"nested_id","id":"nested_id"}`),
			[]byte(`{"a":"foo","id":"top-level-id","c":{"id":"nested_id"}}`),
			"",
		}, {
			// nested id in the schema
			[]byte(`{"title":"t1","properties":{"a":{"type":"string"},"id":{"type":"string"},"c":{"type":"object","properties":{"id":{"type":"string", "id": true}}}}}`),
			[]byte(`{"a":"foo","c":{"id":"nested_id"}}`),
			false,
			[]byte(`{"a":"foo","c.id":"nested_id","id":"nested_id"}`),
			[]byte(`{"a":"foo","c":{"id":"nested_id"}}`),
			"",
		}, {
			// nested id in the schema, but missing in the payload
			[]byte(`{"title":"t1","properties":{"a":{"type":"string"},"id":{"type":"string"},"c":{"type":"object","properties":{"id":{"type":"string", "id": true}}}}}`),
			[]byte(`{"a":"foo","id":"top-level-id","c":{"a":"foo"}}`),
			false,
			nil,
			nil,
			"index has explicitly marked 'c.id' field as 'id' but document is missing that field",
		},
	}
	for _, c := range cases {
		factory, err := schema.NewFactoryBuilder(true).BuildSearch("t1", c.index)
		require.NoError(t, err)

		index := schema.NewSearchIndex(1, "test", factory, nil)
		mp, err := util.JSONToMap(c.doc)
		require.NoError(t, err)

		wt := newWriteTransformer(index, internal.NewTimestamp(), false)
		id, err := wt.getOrGenerateId(c.doc, mp)
		if len(c.expError) > 0 {
			require.ErrorContains(t, err, c.expError)
			continue
		}
		transformed, err := wt.toSearch(id, mp)
		require.NoError(t, err)
		require.NotEmpty(t, id)

		if c.stripId {
			_, found := transformed["id"]
			require.True(t, found)
			delete(transformed, "id")
		}

		// remove created_at before comparison
		_, found := transformed["_tigris_created_at"]
		require.True(t, found)
		delete(transformed, "_tigris_created_at")

		transformedJS, err := util.MapToJSON(transformed)
		require.NoError(t, err)
		require.JSONEq(t, string(c.expTransformed), string(transformedJS))

		rt := newReadTransformer(index)
		original, _, _, err := rt.fromSearch(transformed)
		require.NoError(t, err)
		originalJS, err := util.MapToJSON(original)
		require.NoError(t, err)
		require.JSONEq(t, string(c.expOriginal), string(originalJS))
	}
}
